/*
* Copyright (C) 2019 ~ 2021 Uniontech Software Technology Co.,Ltd.
*
* Author:     yanghongwei <yanghongwei@uniontech.com>
*
* Maintainer: yanghongwei <yanghongwei@uniontech.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "inotify_file_listener.h"

#include <QDebug>
#include <QDateTime>

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/inotify.h>
#include <unistd.h>

Inotify::Inotify(QObject *parent)
    : QThread(parent)
{
    fd = inotify_init();
}

Inotify::~Inotify()
{
    close(fd);
}

bool Inotify::isValid()
{
    return fd > 0;
}

bool Inotify::setNotifyObject(const QString &path)
{
    if (m_mapListenPathWd.contains(path))
        return true;

//    qWarning() << "inotify_add_watch " << path;
    int wd = inotify_add_watch(fd, path.toLocal8Bit().data(), IN_ALL_EVENTS);
    if (wd < 0) {
        qWarning() << "inotify_add_watch " << path << " failed";
        return false;
    }

    m_mapListenPathWd[path] = wd;

    return true;
}

void Inotify::removeNotifyObject(const QString &path)
{
    if (m_mapListenPathWd.contains(path)) {
        inotify_rm_watch(fd, m_mapListenPathWd[path]);
        m_mapListenPathWd.remove(path);
    }
}

void Inotify::startListen()
{
    start();
}

void Inotify::run()
{
    int len;
    int nread;
    char buf[BUFSIZ];
    struct inotify_event *event;

    buf[sizeof(buf) - 1] = 0;
    while ((len = read(fd, buf, sizeof(buf) - 1)) > 0) {
        nread = 0;
        while (len > 0) {
            event = (struct inotify_event *)&buf[nread];
            getEvent(event);
            nread = nread + sizeof(struct inotify_event) + event->len;
            len = len - sizeof(struct inotify_event) - event->len;
        }
    }
}

int Inotify::getEvent(const inotify_event *event)
{
    if (event->len <= 0)
        return -1;

    OperationNode node;
    node.m_fileName = QString("%1/%2").arg(m_mapListenPathWd.key(event->wd)).arg(event->name);
    node.m_date = QDateTime::currentDateTime();

    if (event->mask & IN_OPEN) {
//        qDebug() << node.m_date << node.m_fileName << " IN_OPEN";
        node.m_operationType = OP_OPEN;
    }

    if (event->mask & IN_CLOSE) {
        if (event->mask & IN_CLOSE_WRITE) {
//            qDebug() << node.m_date << node.m_fileName << " IN_CLOSE_WRITE";
            node.m_operationType = OP_CLOSE_WRITE;
        } else if (event->mask & IN_CLOSE_NOWRITE) {
//            qDebug() << node.m_date << node.m_fileName << " IN_CLOSE_NOWRITE";
            node.m_operationType = OP_CLOSE_NOWRITE;
        }
    }

    if (event->mask & IN_ACCESS) {
//        qDebug() << node.m_date << node.m_fileName << " IN_ACCESS";
        node.m_operationType = OP_ACCESS;
    }

    if (event->mask & IN_MODIFY) {
//        qDebug() << node.m_date << node.m_fileName << " IN_MODIFY";
        node.m_operationType = OP_MODIFY;
    }

    if (event->mask & IN_ATTRIB) {
//        qDebug() << node.m_date << node.m_fileName << " IN_ATTRIB";
        node.m_operationType = OP_ATTRIB;
    }

    if (event->mask & IN_MOVE) {
        if (event->mask & IN_MOVED_TO) {
//            qDebug() << node.m_date << node.m_fileName << " IN_MOVED_TO";
            node.m_operationType = OP_MOVE_TO;
        } else if (event->mask & IN_MOVED_FROM) {
//            qDebug() << node.m_fileName << " IN_MOVED_FROM";
            node.m_operationType = OP_MOVE_FROM;
        }
    }

    if (event->mask & IN_CREATE) {
//        qDebug() << node.m_date << node.m_fileName << " IN_CREATE";
        node.m_operationType = OP_CREATE;
    }

    if (event->mask & IN_DELETE) {
//        qDebug() << node.m_date << node.m_fileName << " IN_DELETE";
        node.m_operationType = OP_DELETE;
    }

    if (event->mask & IN_DELETE_SELF) {
//        qDebug() << node.m_date << node.m_fileName << " IN_DELETE_SELF";
        node.m_operationType = OP_DELETE_SELF;
    }

    if (event->mask & IN_MOVE_SELF) {
//        qDebug() << node.m_date << node.m_fileName << " IN_MOVE_SELF";
        node.m_operationType = OP_MOVE_SELF;
    }

    emit operationHappen(node);

    return 0;
}
